- jeu. 15 septembre 2016
- Enseignement
- Michel GRIMALDI
- #arduino
Il est souvent très utile de piloter un micro système basé sur un Arduino avec un ordinateur sous Linux (ou sur Windows si vous y tenez absolument). Si vous ne connaissez que le C ou que vous ne voulez pas mettre en œuvre des bibliothèques compliquées telles que Qt ou autre, il existe une solution relativement simple: utiliser la bibliothèque Arduino-Serial-Lib développée par Tod E. Kurt.
La bibliothèque Arduino-Serial-Lib est constituée de fonctions: serialport_init permettant d'initialiser le port de communication serialport_close pour le fermer serialport_writebyte pour écrire un octet sur le port (envoyer) serialport_write pour écrire une chaîne de caractères int serialport_read_until pour lire le port jusqu'à l'obtention d'un caractère particulier serialport_flush pour évacuer les transferts en cours
Les prototypes de ces fonctions sont:
int serialport_init(const char* serialport, int baud) avec serialport, une chane de caractère contenant le nom du port, ex: /dev/ttyUSB0 sous Linux ou COM1 sous Windows, et baud la vitesse de transfert. La fonction renvoie un entier, handle, qui représente la communication en cours et devra être spécifié dans toutes les fonctions suivantes. Si cet handle est -1, l'initialisation du port n'a pas été possible (mauvais ou absence de port série, .etc.)
int serialport_close( int fd ) fd, le handle retourné par la fonction serialport_init et différent de -1
int serialport_writebyte( int fd, uint8_t b) fd, le handle retourné par la fonction serialport_init et différent de -1 b, un octet à écrire
int serialport_write(int fd, const char* str) fd, le handle retourné par la fonction serialport_init et différent de -1 str, la chaîne de caractère à écrire
int serialport_read_until(int fd, char* buf, char until, int buf_max, int timeout) fd, le handle retourné par la fonction serialport_init et différent de -1 buf, le buffer (tableau de caractère) qui recevra les données reçues until, le caractère de fin, lecture jusqu'à la réception de until buf_max, le nombre maximum de caractères à lire timeout, le temps maximum à attendre en secondes
int serialport_flush(int fd) fd, le handle retourné par la fonction serialport_init et différent de -1
Toutes les fonctions précédentes renvoient 0 en cas de succés et -1 en cas d'échec.
Mise en œuvre de la bibliothèque dans un programme c
Si vous ne maîtrisez pas l'édition des liens et l'ajout de bibliothèques extérieures à un programme, une solution consiste à inclure la totalité du code de la bibliothèque dans votre programme. Elle ferait hurler un bon informaticien mais elle présente l'avantage de la simplicité.
Il suffit d'insérer le code suivant en début de programme:
//__________________________________________________________________________________
// arduino-serial-lib -- simple library for reading/writing serial ports
//
// 2006-2013, Tod E. Kurt, http://todbot.com/blog/
//
#include <stdio.h> // Standard input/output definitions
#include <unistd.h> // UNIX standard function definitions
#include <fcntl.h> // File control definitions
#include <errno.h> // Error number definitions
#include <termios.h> // POSIX terminal control definitions
#include <string.h> // String function definitions
#include <sys/ioctl.h>
#include <stdint.h> // Standard types
// uncomment this to debug reads
//#define SERIALPORTDEBUG
// takes the string name of the serial port (e.g. "/dev/tty.usbserial","COM1")
// and a baud rate (bps) and connects to that port at that speed and 8N1.
// opens the port in fully raw mode so you can send binary data.
// returns valid fd, or -1 on error
int serialport_init(const char* serialport, int baud)
{
struct termios toptions;
int fd;
//fd = open(serialport, O_RDWR | O_NOCTTY | O_NDELAY);
fd = open(serialport, O_RDWR | O_NONBLOCK );
if (fd == -1) {
perror("serialport_init: Unable to open port ");
return -1;
}
//int iflags = TIOCM_DTR;
//ioctl(fd, TIOCMBIS, &iflags); // turn on DTR
//ioctl(fd, TIOCMBIC, &iflags); // turn off DTR
if (tcgetattr(fd, &toptions) < 0) {
perror("serialport_init: Couldn't get term attributes");
return -1;
}
speed_t brate = baud; // let you override switch below if needed
switch(baud) {
case 4800: brate=B4800; break;
case 9600: brate=B9600; break;
#ifdef B14400
case 14400: brate=B14400; break;
#endif
case 19200: brate=B19200; break;
#ifdef B28800
case 28800: brate=B28800; break;
#endif
case 38400: brate=B38400; break;
case 57600: brate=B57600; break;
case 115200: brate=B115200; break;
}
cfsetispeed(&toptions, brate);
cfsetospeed(&toptions, brate);
// 8N1
toptions.c_cflag &= ~PARENB;
toptions.c_cflag &= ~CSTOPB;
toptions.c_cflag &= ~CSIZE;
toptions.c_cflag |= CS8;
// no flow control
toptions.c_cflag &= ~CRTSCTS;
//toptions.c_cflag &= ~HUPCL; // disable hang-up-on-close to avoid reset
toptions.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines
toptions.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl
toptions.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
toptions.c_oflag &= ~OPOST; // make raw
// see: http://unixwiz.net/techtips/termios-vmin-vtime.html
toptions.c_cc[VMIN] = 0;
toptions.c_cc[VTIME] = 0;
//toptions.c_cc[VTIME] = 20;
tcsetattr(fd, TCSANOW, &toptions);
if( tcsetattr(fd, TCSAFLUSH, &toptions) < 0) {
perror("init_serialport: Couldn't set term attributes");
return -1;
}
return fd;
}
//______________________________________________________________________
int serialport_close( int fd )
{
return close( fd );
}
//______________________________________________________________________
int serialport_writebyte( int fd, uint8_t b)
{
int n = write(fd,&b,1);
if( n!=1)
return -1;
return 0;
}
//______________________________________________________________________
int serialport_write(int fd, const char* str)
{
int len = strlen(str);
int n = write(fd, str, len);
if( n!=len ) {
perror("serialport_write: couldn't write whole string\n");
return -1;
}
return 0;
}
//________________________________________________________________________________
int serialport_read_until(int fd, char* buf, char until, int buf_max, int timeout)
{
char b[1]; // read expects an array, so we give it a 1-byte array
int i=0;
do {
int n = read(fd, b, 1); // read a char at a time
if( n==-1) return -1; // couldn't read
if( n==0 ) {
usleep( 1 * 1000 ); // wait 1 msec try again
timeout--;
if( timeout==0 ) return -2;
continue;
}
#ifdef SERIALPORTDEBUG
printf("serialport_read_until: i=%d, n=%d b='%c'\n",i,n,b[0]); // debug
#endif
buf[i] = b[0];
i++;
} while( b[0] != until && i < buf_max && timeout>0 );
buf[i] = 0; // null terminate the string
return 0;
}
//___________________________________________________________________________
int serialport_flush(int fd)
{
sleep(2); //required to make flush work, for some reason
return tcflush(fd, TCIOFLUSH);
}
Il ne vous reste plus alors qu'à écrire votre programme principal, main, ici on se contente d'afficher toutes les lignes de texte reçues de la liaison série:
//____________________________________________________________________________
// PROGRAMME PRINCIPAL
// le fait d'inclure les fonctions de la bibliothèque directement dans le programme
// permet de s'affranchir de la rajouter à l'édition des liens
int main(int argc, char **argv)
{
char buffer[100]; // un buffer
int i;
// ouverture du port à 115200 bauds
int fd = serialport_init("/dev/ttyUSB0", 115200);
if (fd==-1) return -1;
// boucle
for ( ; ; ){
// lecture d'une ligne
serialport_read_until(fd, buffer, '\r', 99, 10000);
// suppression de la fin de ligne
for (i=0 ; buffer[i]!='\r' && i<100 ; i++);
buffer[i] = 0;
// écriture du résultat
printf("%s", buffer);
}
// fermeture du port
serialport_flush(fd);
serialport_close(fd);
return 0;
}
Vous trouverez cet exemple de code ici.
Voici une solution simple, voire simpliste, de communiquer avec un arduino qui ne nécessite pas de connaitre le c++ mais fonctionne bien. Vous trouverez sur ce blog une solution plus riche et plus élégante en c++ avec Qt - autre chose!